{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "7fb27b941602401d91542211134fc71a",
   "metadata": {},
   "source": [
    "# Google ADK Example: Human Approval Workflow with AgentOps"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "acae54e37e7d407bbb7b55eff062a284",
   "metadata": {},
   "source": [
    "This notebook demonstrates a complete human approval workflow using the Google ADK (Agent Development Kit), integrated with AgentOps for observability.\n",
    "\n",
    "**Key Features:**\n",
    "- **Sequential Agent Processing:** The workflow uses multiple agents chained together to handle different stages of the approval process.\n",
    "- **External Tool Integration:** An agent interacts with an external tool that simulates (or in this version, directly prompts for) human approval.\n",
    "- **Session State Management:** Information is passed between agents and persisted using session state.\n",
    "- **AgentOps Observability:** All agent actions, tool calls, and LLM interactions are traced and can be viewed in your AgentOps dashboard.\n",
    "- **Interactive Human Input:** The approval step now requires direct input from the user."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9a63283cbaf04dbcab1f6479b197f3a8",
   "metadata": {},
   "source": [
    "## 1. Setup and Dependencies"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8dd0d8092fe74a7c96281538738b07e2",
   "metadata": {},
   "source": [
    "First, let's install the necessary libraries if they are not already present and import them."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "72eea5119410473aa328ad9291626812",
   "metadata": {},
   "outputs": [],
   "source": [
    "%pip install google-adk agentops python-dotenv nest_asyncio asyncio"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "8edb47106e1a46a883d545849b8ab81b",
   "metadata": {},
   "outputs": [],
   "source": [
    "import json\n",
    "import os\n",
    "import asyncio\n",
    "from google.adk.agents import LlmAgent, SequentialAgent\n",
    "from google.adk.tools import FunctionTool\n",
    "from google.adk.runners import Runner\n",
    "from google.adk.sessions import InMemorySessionService\n",
    "from google.genai import types\n",
    "from pydantic import BaseModel, Field\n",
    "import nest_asyncio\n",
    "import agentops\n",
    "from dotenv import load_dotenv"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "10185d26023b46108eb7d9f57d49d2b3",
   "metadata": {},
   "source": [
    "## 2. Configuration and Initialization"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8763a12b2bbd4a93a75aff182afb95dc",
   "metadata": {},
   "source": [
    "Load environment variables (especially `AGENTOPS_API_KEY` and your Google API key for Gemini) and initialize AgentOps."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7623eae2785240b9bd12b16a66d81610",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Load environment variables from .env file\n",
    "load_dotenv()\n",
    "nest_asyncio.apply()\n",
    "AGENTOPS_API_KEY = os.getenv(\"AGENTOPS_API_KEY\") or \"your_agentops_api_key_here\"\n",
    "# Initialize AgentOps - Just 2 lines!\n",
    "agentops.init(AGENTOPS_API_KEY, trace_name=\"adk-human-approval-notebook\", auto_start_session=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7cdc8c89c7104fffa095e18ddfef8986",
   "metadata": {},
   "source": [
    "Define some constants for our application."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b118ea5561624da68c537baed56e602f",
   "metadata": {},
   "outputs": [],
   "source": [
    "APP_NAME = \"human_approval_app_notebook\"\n",
    "USER_ID = \"test_user_notebook_123\"\n",
    "SESSION_ID = \"approval_session_notebook_456\"\n",
    "MODEL_NAME = \"gemini-1.5-flash\"\n",
    "agentops.start_trace(trace_name=APP_NAME, tags=[\"google_adk\", \"notebook\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "938c804e27f84196a10c8828c723f798",
   "metadata": {},
   "source": [
    "## 3. Define Schemas"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "504fb2a444614c0babb325280ed9130a",
   "metadata": {},
   "source": [
    "Pydantic models are used to define the structure of data for approval requests and decisions. This helps with validation and clarity."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "59bbdb311c014d738909a11f9e486628",
   "metadata": {},
   "outputs": [],
   "source": [
    "class ApprovalRequest(BaseModel):\n",
    "    amount: float = Field(description=\"The amount requiring approval\")\n",
    "    reason: str = Field(description=\"The reason for the request\")\n",
    "\n",
    "\n",
    "class ApprovalDecision(BaseModel):\n",
    "    decision: str = Field(description=\"The approval decision: 'approved' or 'rejected'\")\n",
    "    comments: str = Field(description=\"Additional comments from the approver\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b43b363d81ae4b689946ece5c682cd59",
   "metadata": {},
   "source": [
    "## 4. External Approval Tool (with Human Interaction)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8a65eabff63a45729fe45fb5ade58bdc",
   "metadata": {},
   "source": [
    "This tool now directly prompts the user for an approval decision. In a real-world scenario, this might involve sending a notification to an approver and waiting for their response through a UI or API."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "c3933fab20d04ec698c2621248eb3be0",
   "metadata": {},
   "outputs": [],
   "source": [
    "async def external_approval_tool(amount: float, reason: str) -> str:\n",
    "    \"\"\"\n",
    "    Prompts for human approval and returns the decision as a JSON string.\n",
    "    \"\"\"\n",
    "    print(\"🔔 HUMAN APPROVAL REQUIRED:\")\n",
    "    print(f\"   Amount: ${amount:,.2f}\")\n",
    "    print(f\"   Reason: {reason}\")\n",
    "    decision = \"\"\n",
    "    while decision.lower() not in [\"approved\", \"rejected\"]:\n",
    "        decision = input(\"   Enter decision (approved/rejected): \").strip().lower()\n",
    "        if decision.lower() not in [\"approved\", \"rejected\"]:\n",
    "            print(\"   Invalid input. Please enter 'approved' or 'rejected'.\")\n",
    "    comments = input(\"   Enter comments (optional): \").strip()\n",
    "    print(f\"   Decision: {decision.upper()}\")\n",
    "    print(f\"   Comments: {comments if comments else 'N/A'}\")\n",
    "    return json.dumps({\"decision\": decision, \"comments\": comments, \"amount\": amount, \"reason\": reason})\n",
    "\n",
    "\n",
    "# Create the approval tool instance\n",
    "approval_tool = FunctionTool(func=external_approval_tool)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4dd4641cc4064e0191573fe9c69df29b",
   "metadata": {},
   "source": [
    "## 5. Define Agents"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8309879909854d7188b41380fd92a7c3",
   "metadata": {},
   "source": [
    "We define three agents for our workflow:\n",
    "1.  **`PrepareApprovalAgent`**: Extracts details from the user's request.\n",
    "2.  **`RequestHumanApprovalAgent`**: Uses the `external_approval_tool` to get a decision.\n",
    "3.  **`ProcessDecisionAgent`**: Processes the decision and formulates a final response."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "3ed186c9a28b402fb0bc4494df01f08d",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Agent 1: Prepare the approval request\n",
    "prepare_request = LlmAgent(\n",
    "    model=MODEL_NAME,\n",
    "    name=\"PrepareApprovalAgent\",\n",
    "    description=\"Extracts and prepares approval request details from user input\",\n",
    "    instruction=\"\"\"You are an approval request preparation agent.\n",
    "        Your task:\n",
    "        1. Extract the amount and reason from the user's request\n",
    "        2. Store these values in the session state with keys 'approval_amount' and 'approval_reason'\n",
    "        3. Validate that both amount and reason are provided\n",
    "        4. Respond with a summary of what will be submitted for approval\n",
    "    If the user input is missing amount or reason, ask for clarification.\n",
    "    \"\"\",\n",
    "    output_key=\"request_prepared\",\n",
    ")\n",
    "\n",
    "# Agent 2: Request human approval using the tool\n",
    "request_approval = LlmAgent(\n",
    "    model=MODEL_NAME,\n",
    "    name=\"RequestHumanApprovalAgent\",\n",
    "    description=\"Calls the external approval system with prepared request details\",\n",
    "    instruction=\"\"\"You are a human approval request agent.\n",
    "        Your task:\n",
    "        1. Get the 'approval_amount' and 'approval_reason' from the session state\n",
    "        2. Use the external_approval_tool with these values\n",
    "        3. Store the approval decision in session state with key 'human_decision'\n",
    "        4. Respond with the approval status\n",
    "    Always use the exact values from the session state for the tool call.\n",
    "    \"\"\",\n",
    "    tools=[approval_tool],\n",
    "    output_key=\"approval_requested\",\n",
    ")\n",
    "\n",
    "# Agent 3: Process the approval decision\n",
    "process_decision = LlmAgent(\n",
    "    model=MODEL_NAME,\n",
    "    name=\"ProcessDecisionAgent\",\n",
    "    description=\"Processes the human approval decision and provides final response\",\n",
    "    instruction=\"\"\"You are a decision processing agent.\n",
    "        Your task:\n",
    "        1. Check the 'human_decision' from session state\n",
    "        2. Parse the approval decision JSON\n",
    "        3. If approved: congratulate and provide next steps\n",
    "        4. If rejected: explain the rejection and suggest alternatives\n",
    "        5. Provide a clear, helpful final response to the user\n",
    "\n",
    "    Be professional and helpful in your response.\n",
    "    \"\"\",\n",
    "    output_key=\"final_decision\",\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cb1e1581032b452c9409d6c6813c49d1",
   "metadata": {},
   "source": [
    "## 6. Create Sequential Workflow"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "379cbbc1e968416e875cc15c1202d7eb",
   "metadata": {},
   "source": [
    "Combine the agents into a sequential workflow. The `SequentialAgent` ensures that the sub-agents are executed in the specified order."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "277c27b1587741f2af2001be3712ef0d",
   "metadata": {},
   "outputs": [],
   "source": [
    "approval_workflow = SequentialAgent(\n",
    "    name=\"HumanApprovalWorkflowNotebook\",\n",
    "    description=\"Complete workflow for processing approval requests with human oversight\",\n",
    "    sub_agents=[prepare_request, request_approval, process_decision],\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "db7b79bc585a40fcaf58bf750017e135",
   "metadata": {},
   "source": [
    "## 7. Session Management and Runner"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "916684f9a58a4a2aa5f864670399430d",
   "metadata": {},
   "source": [
    "Set up an in-memory session service and the workflow runner."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "1671c31a24314836a5b85d7ef7fbf015",
   "metadata": {},
   "outputs": [],
   "source": [
    "session_service = InMemorySessionService()\n",
    "# Create runner\n",
    "workflow_runner = Runner(agent=approval_workflow, app_name=APP_NAME, session_service=session_service)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "33b0902fd34d4ace834912fa1002cf8e",
   "metadata": {},
   "source": [
    "## 8. Helper Function to Run Workflow"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f6fa52606d8c4a75a9b52967216f8f3f",
   "metadata": {},
   "source": [
    "This function encapsulates the logic to run the workflow for a given user request and session ID."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "f5a1fa73e5044315a093ec459c9be902",
   "metadata": {},
   "outputs": [],
   "source": [
    "async def run_approval_workflow_notebook(user_request: str, session_id: str):\n",
    "    \"\"\"Run the complete approval workflow with a user request in the notebook environment\"\"\"\n",
    "    print(f\"{'=' * 60}\")\n",
    "    print(f\" Starting Approval Workflow for Session: {session_id}\")\n",
    "    print(f\"{'=' * 60}\")\n",
    "    print(f\"User Request: {user_request}\")\n",
    "    # Create user message\n",
    "    user_content = types.Content(role=\"user\", parts=[types.Part(text=user_request)])\n",
    "    step_count = 0\n",
    "    final_response = \"No response received\"\n",
    "    # Run the workflow\n",
    "    async for event in workflow_runner.run_async(\n",
    "        user_id=USER_ID,\n",
    "        session_id=session_id,\n",
    "        new_message=user_content,\n",
    "    ):\n",
    "        if event.author and event.content:\n",
    "            step_count += 1\n",
    "            print(f\"📋 Step {step_count} - {event.author}:\")\n",
    "            if event.content.parts:\n",
    "                response_text = event.content.parts[0].text\n",
    "                print(f\"   {response_text}\")\n",
    "                if event.is_final_response():\n",
    "                    final_response = response_text\n",
    "    session = await session_service.get_session(\n",
    "        app_name=APP_NAME,\n",
    "        user_id=USER_ID,\n",
    "        session_id=session_id,\n",
    "    )\n",
    "    print(f\"{'=' * 60}\")\n",
    "    print(f\"📊 Workflow Complete - Session State ({session_id}):\")\n",
    "    print(f\"{'=' * 60}\")\n",
    "    for key, value in session.state.items():\n",
    "        print(f\"   {key}: {value}\")\n",
    "    print(f\"🎯 Final Response: {final_response}\")\n",
    "    return final_response"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cdf66aed5cc84ca1b48e60bad68798a8",
   "metadata": {},
   "source": [
    "## 9. Main Execution Logic"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "28d3efd5258a48a79c179ea5c6759f01",
   "metadata": {},
   "source": [
    "This cell contains the main logic to run the workflow with a few test cases. Each test case will run in its own session."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3f9bc0b9dd2c44919cc8dcca39b469f8",
   "metadata": {},
   "outputs": [],
   "source": [
    "async def main_notebook():\n",
    "    test_requests = [\n",
    "        \"I need approval for $750 for team lunch and celebrations\",\n",
    "        \"Please approve $3,000 for a conference ticket and travel expenses\",\n",
    "        \"I need $12,000 approved for critical software licenses renewal\",\n",
    "    ]\n",
    "    for i, request in enumerate(test_requests, 1):\n",
    "        current_session_id = f\"approval_session_notebook_{456 + i - 1}\"\n",
    "        # Create the session before running the workflow\n",
    "        await session_service.create_session(app_name=APP_NAME, user_id=USER_ID, session_id=current_session_id)\n",
    "        print(f\"Created session: {current_session_id}\")\n",
    "        await run_approval_workflow_notebook(request, current_session_id)\n",
    "\n",
    "\n",
    "try:\n",
    "    asyncio.run(main_notebook())\n",
    "    agentops.end_trace(end_state=\"Success\")\n",
    "except Exception as e:\n",
    "    print(f\"Error: {e}\")\n",
    "    agentops.end_trace(end_state=\"Error\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
